﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Media;
using LukasKopenec.LocalizationSupport;
using Notoric.Commanding.CommandBase;
using Notoric.Model.Packaging;
using Notoric.ViewModel.Internal;

namespace Notoric.ViewModel.Elements
{
    /// <summary>
    /// Adapts a <see cref="CompositionPart"/> and <see cref="Notoric.Model.Elements.CompositionElement"/>.
    /// </summary>
    public class Composition : ViewModelElement, IProjectExplorerItem
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Composition"/> instance.
        /// </summary>
        /// <param name="adaptedPart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// Adapted composition part.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Project"/>
        /// </para>
        /// <para>
        /// The project element that directly owns this composition.
        /// </para>
        /// </param>
        /// <param name="name">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name of the composition.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="adaptedPart"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="NullReferenceException">
        /// <paramref name="owner"/> is <see langword="null"/>.
        /// The values of the <see cref="ViewModelElement.UndoStack"/> and <see cref="ViewModelElement.RedoStack"/> properties
        /// are passed directly to the base class (<see cref="ViewModelElement"/>) constructor. Thus, there is not
        /// an occasion to react with a more appropriate exception.
        /// </exception>
        public Composition(CompositionPart adaptedPart, Project owner, string name)
            : base(adaptedPart, owner, owner.UndoStack, owner.RedoStack)
        {
            this.name = name;
            var pathElement = AdaptedPart.Package.ProjectPart.Project.Compositions.First(pe => pe.URI == AdaptedPart.Uri);
            pathElement.PropertyChanged += new PropertyChangedEventHandler(pathElement_PropertyChanged);

            projectExplorerHelper = new ProjectExplorerItemHelper(owner, this);
            projectExplorerHelper.SetIcon(EmbeddedBitmapLoader.Load("Pictures.ProjectExplorer.Composition.png"));
            projectExplorerHelper.PropertyChanged += new PropertyChangedEventHandler(projectExplorerHelper_PropertyChanged);
            LinearView = new LinearCompositionView(adaptedPart.LinearView, this);
            projectExplorerHelper.Children.Add(LinearView);
            
            CreateContextMenu();
        }

        #endregion

        #region ViewModelElement Members

        /// <inheritdoc/>
        public override string ElementNameKey
        {
            get { return elementNameKey; }
        }

        #endregion

        #region IProjectExplorerItem Members

        /// <inheritdoc/>
        public ContextMenu ContextMenu
        {
            get { return projectExplorerHelper.ContextMenu; }
        }

        /// <inheritdoc/>
        public string DisplayName
        {
            get
            {
                return name;
            }
        }

        /// <inheritdoc/>
        public ImageSource Icon
        {
            get { return projectExplorerHelper.Icon; }
        }

        /// <inheritdoc/>
        public bool IsExpanded
        {
            get
            {
                return projectExplorerHelper.IsExpanded;
            }
            set
            {
                projectExplorerHelper.IsExpanded = value;
            }
        }

        /// <inheritdoc/>
        public bool IsInEditMode
        {
            get { return projectExplorerHelper.IsInEditMode; }
            set { projectExplorerHelper.IsInEditMode = value; }
        }

        /// <inheritdoc/>
        public bool IsSelected
        {
            get
            {
                return projectExplorerHelper.IsSelected;
            }
            set
            {
                projectExplorerHelper.IsSelected = value;
            }
        }

        /// <inheritdoc/>
        public string Name
        {
            get { return name; }
            set 
            {
                var cmd = new RenamePartCommand(UndoStack, RedoStack)
                {
                    PathCollection = AdaptedPart.Package.ProjectPart.Project.Compositions,
                    RenamedPart = this,
                    VirtualPath = ((Directory)Parent).VirtualPath + "/" + value
                };
                cmd.Execute(null);

                RaisePropertyChanged("Name");
                RaisePropertyChanged("DisplayName");
            }
        }

        /// <inheritdoc/>
        public IProjectExplorerItem Parent
        {
            get { return projectExplorerHelper.Parent; }
            set { projectExplorerHelper.Parent = value; }
        }

        /// <inheritdoc/>
        public ObservableCollection<IProjectExplorerItem> Children
        {
            get { return projectExplorerHelper.Children; }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the collection of composers of the adapted composition has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void composers_CollectionChanged(object sender, Model.Collections.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case Model.Collections.NotifyCollectionChangedAction.Add:
                    {
                        int index = e.NewStartingIndex;
                        foreach (Model.Elements.PersonElement person in e.NewItems)
                            composers.Insert(index++, new Person(person, this, UndoStack, RedoStack));
                        break;
                    }
                case Model.Collections.NotifyCollectionChangedAction.Move:
                    composers.Move(e.OldStartingIndex, e.NewStartingIndex);
                    break;
                case Model.Collections.NotifyCollectionChangedAction.Remove:
                    foreach (Model.Elements.PersonElement person in e.OldItems)
                        composers.Remove(composers.First(p => p.AdaptedElement == person));
                    break;
                case Model.Collections.NotifyCollectionChangedAction.Replace:
                    {
                        foreach (Model.Elements.PersonElement person in e.OldItems)
                            composers.Remove(composers.First(p => p.AdaptedElement == person));

                        int index = e.NewStartingIndex;
                        foreach (Model.Elements.PersonElement person in e.NewItems)
                            composers.Insert(index++, new Person(person, this, UndoStack, RedoStack));
                        break;
                    }
                case Model.Collections.NotifyCollectionChangedAction.Reset:
                    composers.Clear();
                    break;
            }
        }

        /// <summary>
        /// Occurs when the collection of lyricists of the adapted composition has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void lyricists_CollectionChanged(object sender, Model.Collections.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case Model.Collections.NotifyCollectionChangedAction.Add:
                {
                    int index = e.NewStartingIndex;
                    foreach (Model.Elements.PersonElement person in e.NewItems)
                        lyricists.Insert(index++, new Person(person, this, UndoStack, RedoStack));
                    break;
                }
                case Model.Collections.NotifyCollectionChangedAction.Move:
                    lyricists.Move(e.OldStartingIndex, e.NewStartingIndex);
                    break;
                case Model.Collections.NotifyCollectionChangedAction.Remove:
                    foreach (Model.Elements.PersonElement person in e.OldItems)
                        lyricists.Remove(lyricists.First(p => p.AdaptedElement == person));
                    break;
                case Model.Collections.NotifyCollectionChangedAction.Replace:
                {
                    foreach (Model.Elements.PersonElement person in e.OldItems)
                        lyricists.Remove(lyricists.First(p => p.AdaptedElement == person));

                    int index = e.NewStartingIndex;
                    foreach (Model.Elements.PersonElement person in e.NewItems)
                        lyricists.Insert(index++, new Person(person, this, UndoStack, RedoStack));
                    break;
                }
                case Model.Collections.NotifyCollectionChangedAction.Reset:
                    lyricists.Clear();
                    break;
            }
        }

        /// <summary>
        /// Occurs when the collection of parts of the adapted composition has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Parts_CollectionChanged(object sender, Model.Collections.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case Model.Collections.NotifyCollectionChangedAction.Add:
                {
                    var index = e.NewStartingIndex;
                    foreach (Model.Elements.PartDescriptorElement part in e.NewItems)
                        parts.Insert(index++, new PartDescriptor(part, this));
                    break;
                }
                case Model.Collections.NotifyCollectionChangedAction.Move:
                    parts.Move(e.OldStartingIndex, e.NewStartingIndex);
                    break;
                case Model.Collections.NotifyCollectionChangedAction.Remove:
                    foreach (Model.Elements.PartDescriptorElement part in e.OldItems)
                        parts.Remove(parts.First(p => p.AdaptedElement == part));
                    break;
                case Model.Collections.NotifyCollectionChangedAction.Replace:
                {
                    foreach (Model.Elements.PartDescriptorElement part in e.OldItems)
                        parts.Remove(parts.First(p => p.AdaptedElement == part));
                    var index = e.NewStartingIndex;
                    foreach (Model.Elements.PartDescriptorElement part in e.NewItems)
                        parts.Insert(index++, new PartDescriptor(part, this));
                    break;
                }
                case Model.Collections.NotifyCollectionChangedAction.Reset:
                    parts.Clear();
                    break;
            }
        }

        /// <summary>
        /// Occurs when the virtual path to this composition has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void pathElement_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "VirtualPath")
            {
                var path = ((Model.Collections.PathElement)sender).VirtualPath;
                name = path.Substring(path.LastIndexOf('/') + 1);

                RaisePropertyChanged("Name");
                RaisePropertyChanged("DisplayName");
            }
        }

        /// <summary>
        /// Occurs when the value of some property of the <see cref="projectExplorerHelper"/> has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler just resends the event via its own <see cref="ViewModelElement.PropertyChanged"/>.
        /// </remarks>
        /// <param name="sender">The object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void projectExplorerHelper_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            RaisePropertyChanged(e);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Creates the context menu for this composition.
        /// </summary>
        private void CreateContextMenu()
        {
            var menu = new ContextMenu();
            var mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.Open.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.CompositionOpen");
            mitem.Command = ProjectExplorerCommands.OpenCommand;
            mitem.CommandParameter = this;
            menu.Items.Add(mitem);

            mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.EditMetadata.png", 16, 16);
            mitem.Header = "Edit Properties";
            mitem.Command = ProjectExplorerCommands.EditMetadataCommand;
            mitem.CommandParameter = this;
            menu.Items.Add(mitem);

            mitem = new MenuItem();
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.Rename");
            mitem.Command = ProjectExplorerCommands.RenameCommand;
            mitem.CommandParameter = this;
            menu.Items.Add(mitem);

            menu.Items.Add(new Separator());

            mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.Delete.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.Delete");
            mitem.Command = ProjectExplorerCommands.DeleteCommand;
            mitem.CommandParameter = this;
            menu.Items.Add(mitem);

            projectExplorerHelper.SetContextMenu(menu);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Adds new measures to the composition.
        /// </summary>
        /// <param name="measuresCount">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The measures count.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The <paramref name="measuresCount"/> is less than or equal to 0.
        /// </exception>
        public void AddMeasures(int measuresCount)
        {
            if (measuresCount <= 0)
                throw new ArgumentOutOfRangeException("The measures count must be positive.", "measuresCount");

            var modelComposition = AdaptedElement as Model.Elements.CompositionElement;

            for (int i = 0; i < measuresCount; ++i)
            {
                var measure = modelComposition.CreateMeasure();
                modelComposition.Measures.Add(measure);
            }
        }

        /// <summary>
        /// Adds an organ part to this composition.
        /// </summary>
        /// <param name="name">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The name of the part.
        /// </para>
        /// </param>
        /// <param name="abbreviation">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The abbreviation of the part's name.
        /// </para>
        /// </param>
        public void AddOrganPart(string name, string abbreviation)
        {
            var modelComposition = AdaptedElement as Model.Elements.CompositionElement;
            var modelPart = modelComposition.CreatePartDescriptor(name);
            var cwData = new Model.Elements.CWNotation.CWPartData(AdaptedPart as Model.Packaging.CompositionPart, modelPart);

            modelPart.Abbreviation = abbreviation;
            modelPart.CustomData.Add(cwData);

            var righthand = cwData.AddStaffDescriptor(5, 0, 0.7);
            var leftHand = cwData.AddStaffDescriptor(5, 1, 0.7);
            var pedal = cwData.AddStaffDescriptor(5, 2, 0.7);

            leftHand.DefaultClefPosition = 6;
            leftHand.DefaultClefType = Model.Elements.CWNotation.ClefType.F;
            pedal.DefaultClefPosition = 6;
            pedal.DefaultClefType = Model.Elements.CWNotation.ClefType.F;

            cwData.AddStaffConnector(typeof(Model.Elements.CWNotation.Brace), leftHand, righthand);
            cwData.AddBarlineSegment(righthand, pedal);

            modelComposition.Parts.Add(modelPart);
        }

        /// <summary>
        /// Adds a piano part to this composition.
        /// </summary>
        /// <param name="name">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The name of the part.
        /// </para>
        /// </param>
        /// <param name="abbreviation">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The abbreviation of the part's name.
        /// </para>
        /// </param>
        public void AddPianoPart(string name, string abbreviation)
        {
            var modelComposition = AdaptedElement as Model.Elements.CompositionElement;
            var modelPart = modelComposition.CreatePartDescriptor(name);
            var cwData = new Model.Elements.CWNotation.CWPartData(AdaptedPart as Model.Packaging.CompositionPart, modelPart);

            modelPart.Abbreviation = abbreviation;
            modelPart.CustomData.Add(cwData);
            
            var righthand = cwData.AddStaffDescriptor(5, 0, 0.7);
            var leftHand = cwData.AddStaffDescriptor(5, 1, 0.7);

            leftHand.DefaultClefPosition = 6;
            leftHand.DefaultClefType = Model.Elements.CWNotation.ClefType.F;

            cwData.AddStaffConnector(typeof(Model.Elements.CWNotation.Brace), leftHand, righthand);
            cwData.AddBarlineSegment(righthand, leftHand);

            modelComposition.Parts.Add(modelPart);
        }

        /// <summary>
        /// Adds a single-staff part to this composition.
        /// </summary>
        /// <param name="name">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The name of the part.
        /// </para>
        /// </param>
        /// <param name="abbreviation">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The abbreviation of the part's name.
        /// </para>
        /// </param>
        /// <param name="clefType">
        /// <para>
        /// Type: <see cref="CWNotation.ClefType"/>
        /// </para>
        /// <para>
        /// The type of the default staff clef.
        /// </para>
        /// </param>
        /// <param name="clefPosition">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The staff position of the default staff clef.
        /// </para>
        /// </param>
        public void AddSingleStaffPart(string name, string abbreviation, CWNotation.ClefType clefType, int clefPosition)
        {
            var modelComposition = AdaptedElement as Model.Elements.CompositionElement;
            var modelPart = modelComposition.CreatePartDescriptor(name);
            var cwData = new Model.Elements.CWNotation.CWPartData(AdaptedPart as Model.Packaging.CompositionPart, modelPart);

            modelPart.Abbreviation = abbreviation;
            modelPart.CustomData.Add(cwData);

            var staff = cwData.AddStaffDescriptor(5, 0, 0.7);

            staff.DefaultClefType = (Model.Elements.CWNotation.ClefType)Enum.Parse(typeof(Model.Elements.CWNotation.ClefType), clefType.ToString());
            staff.DefaultClefPosition = clefPosition;
            
            cwData.AddBarlineSegment(staff, staff);

            modelComposition.Parts.Add(modelPart);
        }

        /// <summary>
        /// Creates an undoable command for setting the date of the creation of the composition.
        /// </summary>
        /// <param name="date">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The date.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetDateCommand(string date)
        {
            return new SetterCommand<string>(this, "Date", date, UndoStack, RedoStack)
            {
                Name = "Change Composition Date"
            };
        }

        /// <summary>
        /// Creates an undoable command for setting the description of the composition.
        /// </summary>
        /// <param name="description">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The description.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetDescriptionCommand(string description)
        {
            return new SetterCommand<string>(this, "Description", description, UndoStack, RedoStack)
            {
                Name = "Change Composition Description"
            };
        }

        /// <summary>
        /// Creates an undoable command for setting the sub title of the composition.
        /// </summary>
        /// <param name="subTitle">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The subTitle.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetSubTitleCommand(string subTitle)
        {
            return new SetterCommand<string>(this, "SubTitle", subTitle, UndoStack, RedoStack)
            {
                Name = "Change Composition SubTitle"
            };
        }

        /// <summary>
        /// Creates an undoable command for setting the title of the composition.
        /// </summary>
        /// <param name="title">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The title.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetTitleCommand(string title)
        {
            return new SetterCommand<string>(this, "Title", title, UndoStack, RedoStack)
            {
                Name = "Change Composition Title"
            };
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the composers of this composition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ObservableCollection{T}"/> of <see cref="Person"/>
        /// </para>
        /// <para>
        /// The composers.
        /// </para>
        /// </value>
        public ObservableCollection<Person> Composers
        {
            get 
            {
                if (composers == null)
                {
                    composers = new ObservableCollection<Person>();
                    var adaptedCollection = ((Model.Elements.CompositionElement)AdaptedElement).Composers;
                    foreach (var composer in adaptedCollection)
                        composers.Add(new Person(composer, this, UndoStack, RedoStack));
                    adaptedCollection.CollectionChanged += new EventHandler<Model.Collections.NotifyCollectionChangedEventArgs>(composers_CollectionChanged);
                }
                return composers; 
            }
        }

        /// <summary>
        /// Gets or sets the date of the creation of the composition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The date of the creation of the composition.
        /// </para>
        /// </value>
        public string Date
        {
            get { return GetPropertyValue<string>("Date"); }
            set
            {
                if (UnsavedChanges.ContainsKey("Date"))
                {
                    var cmd = (SetterCommand<string>)UnsavedChanges["Date"];
                    cmd.Value = value;
                }
                else
                    UnsavedChanges["Date"] = CreateSetDateCommand(value);

                RaisePropertyChanged("Date");
            }
        }

        /// <summary>
        /// Gets or sets the description of the composition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The description of the composition.
        /// </para>
        /// </value>
        public string Description
        {
            get { return GetPropertyValue<string>("Description"); }
            set
            {
                if (UnsavedChanges.ContainsKey("Description"))
                {
                    var cmd = (SetterCommand<string>)UnsavedChanges["Description"];
                    cmd.Value = value;
                }
                else
                    UnsavedChanges["Description"] = CreateSetDescriptionCommand(value);

                RaisePropertyChanged("Description");
            }
        }

        /// <summary>
        /// Gets the linear view of this composition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="LinearCompositionView"/>
        /// </para>
        /// <para>
        /// The linear view of this composition.
        /// </para>
        /// </value>
        public LinearCompositionView LinearView
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the lyricists of this composition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ObservableCollection{T}"/> of <see cref="Person"/>
        /// </para>
        /// <para>
        /// The lyricists of this composition.
        /// </para>
        /// </value>
        public ObservableCollection<Person> Lyricists
        {
            get 
            {
                if (lyricists == null)
                {
                    lyricists = new ObservableCollection<Person>();
                    var adaptedCollection = ((Model.Elements.CompositionElement)AdaptedElement).Lyricists;
                    foreach (var lyricist in adaptedCollection)
                        lyricists.Add(new Person(lyricist, this, UndoStack, RedoStack));
                    adaptedCollection.CollectionChanged += new EventHandler<Model.Collections.NotifyCollectionChangedEventArgs>(lyricists_CollectionChanged);
                }
                return lyricists; 
            }
        }

        /// <summary>
        /// Gets the total count of measures in this composition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The total count of measures in this composition.
        /// </para>
        /// </value>
        public int MeasuresCount
        {
            get { return ((Model.Elements.CompositionElement)AdaptedElement).MeasuresCount; }
        }

        /// <summary>
        /// Gets the collection of parts in this composition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ObservableCollection{T}"/> of <see cref="PartDescriptor"/>
        /// </para>
        /// <para>
        /// The collection of parts in this composition.
        /// </para>
        /// </value>
        public ObservableCollection<PartDescriptor> Parts
        {
            get 
            {
                if (parts == null)
                {
                    parts = new ObservableCollection<PartDescriptor>();

                    foreach (var part in ((Model.Elements.CompositionElement)AdaptedElement).Parts)
                        parts.Add(new PartDescriptor(part, this));

                    ((Model.Elements.CompositionElement)AdaptedElement).Parts.CollectionChanged += new EventHandler<Model.Collections.NotifyCollectionChangedEventArgs>(Parts_CollectionChanged);
                }
                
                return parts; 
            }
        }

        /// <summary>
        /// Gets or sets the sub title of the composition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The sub title of the composition.
        /// </para>
        /// </value>
        public string SubTitle
        {
            get { return GetPropertyValue<string>("SubTitle"); }
            set
            {
                if (UnsavedChanges.ContainsKey("SubTitle"))
                {
                    var cmd = (SetterCommand<string>)UnsavedChanges["SubTitle"];
                    cmd.Value = value;
                }
                else
                    UnsavedChanges["SubTitle"] = CreateSetSubTitleCommand(value);

                RaisePropertyChanged("SubTitle");
            }
        }

        /// <summary>
        /// Gets or sets the title of the composition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The title of the composition.
        /// </para>
        /// </value>
        public string Title
        {
            get { return GetPropertyValue<string>("Title"); }
            set
            {
                if (UnsavedChanges.ContainsKey("Title"))
                {
                    var cmd = (SetterCommand<string>)UnsavedChanges["Title"];
                    cmd.Value = value;
                }
                else
                    UnsavedChanges["Title"] = CreateSetTitleCommand(value);

                RaisePropertyChanged("Title");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Composers"/> property.
        /// </summary>
        private ObservableCollection<Person> composers;
        /// <summary>
        /// Holds a user friendly name of this element.
        /// </summary>
        private const string elementNameKey = "ViewModel.ElementNames.Composition";
        /// <summary>
        /// Holds the value of the <see cref="Lyricists"/> property.
        /// </summary>
        private ObservableCollection<Person> lyricists;
        /// <summary>
        /// Holds the name of this composition.
        /// </summary>
        private string name;
        /// <summary>
        /// Holds the value of the <see cref="Parts"/> property.
        /// </summary>
        private ObservableCollection<PartDescriptor> parts;
        /// <summary>
        /// Holds the project explorer helper implementation.
        /// </summary>
        private ProjectExplorerItemHelper projectExplorerHelper;

        #endregion
    }
}
